home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-09-12 | 52.3 KB | 1,044 lines | [TEXT/CCL2] |
- ;;; RCS.LISP (Simple Revision Control System)
- ;;; Version 3.1, July '94
- ;;; Functions for managing the editing of project code by multiple people.
- ;;; Hacked by David Neves - neves@ils.nwu.edu
- ;;;
- (in-package :ccl)
-
- #|
- Changes:
- allender/neves
- (7/94) Misc. minor changes for multiple projects.
- neves (6/94) Fix bug in backup when saving newly created files -- files not on the server.
- neves A backup directory is now available. Unlocking a file will have the older version
- placed in a backup directory.
- neves &
- allender(5/94) Multiple projects are now handled
- neves (1/94) Add *files-to-copy* so that the user can specify files and directories
- to copy. see documentation near the defvar.
- neves (12/93) Close an editor file that is the same name as a file you are locking.
- When updating a directory delete any fasl files corresponding to
- .lisp files that are copied over.
- neves (11/93) Change all :overwrites to :supersedes in copy/rename-file.
- Have lock-project-file remember directory the file came from so
- that the next lock-project-file will start at that directory.
- Change the setq of *files-not-to-copy* in init-rcs to pushnews so that
- the user can initialize *files-not-to-copy* in rcs-init.lisp.
- Adding Jeff's change below generated an error with a comment that
- didn't have a comment character in front of it. Fixed.
- neves (11/93) Add Jeff Lind's changes to lock files on the server. Have rcs
- check for a ccl:rcs-init.lisp file to load. rcs-init.lisp can
- be used to set defparameters below without having to recompile
- and reload this software.
- Also put a dialog front end to
- list of files/directories to not copy on an update.
- neves (11/93) Put in locking protocol so that it is not possible for two people
- to lock or unlock files at the same time. This eliminates possible
- inconsistancies to the file that has data on locked files.
- Thanks Steve Feist for the suggestion on how to do this.
- neves (11/93) Allow none copying of subdirectories in project tree by putting the
- directory name in *files-not-to-copy*. Update will ask the user to
- specify directories they don't want to copy.
- Put in a menu choice to get
- the comments about changes for the currently displayed project file.
- Delete minor change to get-string-from-user. It didn't work.
- neves (10/93) Minor change to get-string-from-user.
- neves (7/27/93) Changes to make logical pathname stuff work for a user on the server
- jona (2/2/93) Wrap menu call to copy-directory in a eval-enqueue.
- neves (1/15/93) As per Kemi's suggestion, have init-rcs put a call to itself in
- *lisp-startup-functions*.
- neves (1/14/93) use *home-directory-o* to store logical pathname of home directory.
- jona (1/6/93) balloon help and code to better display log file.
- neves (12/10/92) copy-directory now prints out name of file copied. Don't ask if non-text
- files should be edited when locking them.
- neves (11/12/92) Have button to Forget files be labeled as Forget rather than Unlock.
- neves (10/29/92) Check to see if *server-volume* is mounted. When locking, don't copy
- the file over if you already have the most recent version.
- Other misc changes.
- neves (10/16/92) Add *files-not-to-copy* to prevent RCS bookkeeping files from being
- copied to a users local disk. Other misc changes.
- neves (10/15/92) Add help and viewing of log file
- neves (10/15/92) Lock file before copying to the local disk.
- neves (10/14/92) Fix pathname bugs for released MCL 2.0, add copy-directory function
- neves (1/21) Make a variable to hold folder of server volume on server machine
- neves (1/7/92) Server now has a separate working directory.
- neves (12/23/91) Updated to MACL 2.0 Beta
-
- =========================================================================================
- Documentation:
- On any large project there is a danger of 2 people editing the same file at the same time.
- Most likely one person's changes will be lost. This software allows someone to "lock" a
- file so that no one else can edit it. When the user is finished editing the file they
- can "unlock" the file so that others can edit it.
- Project files are kept on a central server. Locking a file copies that file to the user's
- local hard disk and stores the file name in a list of locked files on the central server.
- When the user unlocks the file, the file is copied back to the server and the file name
- is removed from the list of locked files.
- The project directory on the server may be hierarchical. Files copied from it
- will be put in the same relative position on the user hard disk.
- For example, the file server:foo:bar might be copied to
- user:foo:bar. "foo" is a subfolder where bar is located.
- =========================================================================================
-
- User choices from the "lockfile" menu:
- - Lock a file. This brings up a dialog so that the user can choose a file to lock. If
- the file is already locked then the user gets an error message. Locking a file
- copies the file from the server to the local hard disk. Then the name of the locked
- file is stored in a special file ("locked-file-list") on the server.
- - Unlock a file and copy to server. This brings up a dialog with all your locked files.
- Select 1 or more files (with shift-click) to unlock. The files are copied back to the
- server and their names are deleted from "locked-file-list".
- - Unlock a file, but don't copy to server. This is like the choice above but the files
- are not copied to the server. Useful when the user changes his/her mind about making
- the changes permanent.
- - Copy a newly created file to the server. The user has just created a file on his/her
- hard disk. To move it to the server choose this.
- - Update - copy server directory to local disk. Updates all files.
- - Show all locked files. Show a list of all the locked files, along with who locked them.
-
- Hardware needed:
- Each user needs a Macintosh with access to an Appleshare network.
- You need a server machine that can be mounted from other Macs.
-
- Software needed:
- System 7.0 (or greater) & MACL 2.0 (or greater)
-
- To install:
- Simply load this file after changing the defparameters below.
- The LockFile menu choice will install itself.
-
- To do:
- - from Kemi : use apple events to be able to edit other than text files
- - It would be nice if this software mounted the server volume. I don't know how to do this.
- HOWEVER if the users create an alias on the desktop with the same same as the server volume
- this software will automatically open it up (a probe-file will do it).
- - Change log file to allow multi-line comments
-
- Known bugs:
- - Someone can keep a lock on the lock list file too long by not responding to the
- dialog that asks if they want to lock a project file that is older than one on their
- hard disk. This only happens during lock-project-file.
- - If the client and server clocks are more than 15 minutes out of phase then rcs will not do the
- right thing on updates and locking/unlocking files. This usually happens around the change to/from
- daylight savings time. See "Server and workstation clock times" in the Appleshare
- questions and answers in Apple Tech note NW 515 for more information on this.
-
- Kludge comments:
- Because a person on a server machine cannot mount their own machine
- I have a bunch of special case code that allows one to use this software
- on a server machine.
- (thus the need for *server-name* & *folder-of-server-volume-on-server*)
-
- Changes you have to make:
- The only changes you should need to make for your project are to the defparameters below.
- |#
-
- ;;; ------------------------------------------------------------------------------------------------
- ;;; Change the following defparameters for your project.
-
- ;(defparameter *server-volume* "Data Storage - AK Lab:MJC backup:MOPED Server:")
- (defparameter *server-volume* "SC-builder:test:")
- ;Server folder where the project files are kept. The first part of it is what users
- ;connect to (i.e. it is the shared folder).
- ;e.g. sc-builder:test: -- users connect to folder sc-builder
- ;I recommend that users create an alias with this name on their desktop so that
- ;the server-volume is automatically opened when rcs is initialized.
-
- ;(defparameter *home-directory-o* "ccl:MOPED;")
- (defparameter *home-directory-o* "ccl:SC-builder;")
- ;Local home directory where the project files are kept for all users.
- ;This is where a file ends up when locked and copied to the users hard disk.
- ;It must be understood by all user machines so it is recommended it be put under
- ;ccl: or home:. Or it can also be set in rcs-init.lisp.
- ;[[Note use of CL style logical pathname (page 628 of Steele)
- ;with semicolin separating directories.]]
- ;e.g. ccl:myproject;
-
- ;;; The following two defparamters need to be set if a user is going to be using the server machine.
- ;(defparameter *server-name* "Chung's Macintosh")
- (defparameter *server-name* "neves")
- ;If nobody is using the server machine then you don't need to set this.
- ;This is the name of the machine that is the server -- the Macintosh name
- ;in sharing setup.
- ;e.g. "neves"
-
-
- ;(defparameter *folder-of-server-volume-on-server* "MJC backup:")
- (defparameter *folder-of-server-volume-on-server* "hd:applications:")
- ;Used only by the person using the server machine. If nobody is using the
- ;server machine then you don't need to set this.
- ;This is the folder on the server that contains the *server-volume*.
- ;e.g. hd:applications:
- ;so the path to the directory users will copy from is hd:applications:sc-builder:test:
-
- ;;; The following two defparameters need to be set if you want backups saved of the files that
- ;;; someone unlocks.
- (defparameter *rcs-backup-folder* "SC-builder:test:backups:")
- ;if non nil then use as a location for a backup folder
- ;to save older versions of unlocked files.
- ;This name is also automatically placed on *files-not-to-copy*
- (defparameter *rcs-backup-versions* 2) ;use nil for unlimited. 2, keep last 2 versions
-
-
- ;;; The following names do not need to be changed.
- (defparameter *filename-locked-file-list-file* "locked-file-list")
- ;File for list of locked files
- (defparameter *filename-log-file* "logfile")
- ;File for documentation on changes made to files
- (defparameter *filename-lock-name* "!for locking protocol!")
- ;name of temporary file used to control
- ;who currently has access to locking/unlocking
- (defparameter *rcs-init-file-name* "ccl:rcs-init.lisp"); name of init file
-
- ;;; -----------------------------------------------------------------------------------------
- ;;; More optional defvars to set. Can be set in rcs-init.lisp or this file.
- (defvar *files-not-to-copy* nil) ; list of files or directories not to update to local disk from server
- (defvar *files-to-copy* nil) ; list of files or directories to update to local disk. (see doc below)
- (defvar *rcs-project-list* nil) ; list of projects you are working on. see doc below
- (defvar *current-project* nil) ; current project working on.
-
- ;;; -----------------------------------------------------------------------------------------
- ;;;;; Internally set defvars
- (defvar *locked-file-list-file*) ; full pathname of locked-file-list-file
- (defvar *log-file*) ; full pathname of log file
- (defvar *home-directory*) ; set from home-directory-o above
- (defvar *expanded-server-volume*); expanded version of server-volume
- (defvar *locked-file-list*) ; temporary list holding the contents of locked-file-list-file
- (defvar *last-directory-locked* nil) ;most recent server directory accessed from lock-project-file.
- (defvar *rcs-menu*) ; lock file menu
- (defvar *lock-name*) ; controlling file to prevent multiple people from locking
- ; at the same time. expanded version of *filename-lock-name*
- (defvar *lock-id* nil) ; id of open lock to be closed later
- (defparameter *max-lock-wait-time* 10) ; max number of seconds to wait for a lock/unlock
- ;;; -----------------------------------------------------------------------------------------
- #|
- Files-not-to-copy and files-to-copy
- Normally all files are copied from the source disk to the destination disk. The user can change
- this by specifying files or directories to copy or not to copy. Examples:
- specify a parent directory to copy and a child directory not to copy
- or
- specify a parent directory to not copy and a child directory to copy
- You can set these lists in rcs-init.lisp if you like. Each directory or file should
- be a string.
- e.g.
- (setq *files-not-to-copy* '("server:proj-backup:")) ;don't copy directory proj-backup
- (setq *files-to-copy* '("server:proj-backup:picts:")) ;however do copy the picts
-
- --------------------------------- Project stuff
- *rcs-project-list* is a list of projects. This allows the user to switch between projects
- so that he or she can lock/unlock files in those projects.
- *current-project* is the current project that the user is accessing.
-
- Example code to put in rcs-init.lisp or in this file
- ;;; (name-of-project server-name home-directory server-volume folder-of-server-volume backup-folder backup-versions)
- (setq *rcs-project-list*
- '(
- ("project1" "neves" "ccl:sc-builder;" "sc-builder:" "hd:applications:")
- ("project2" "neves" nil "foo:" "hd:fee:") ))
- ;nil default in project2 set by loading rcs-init.lisp in (init-rcs)
- ;e.g. (in rcs-init.lisp)
- ;(when (equal *current-project* "project2")
- ; (setq *home-directory-o* "hd:baz;"))
- (when (null *current-project*) (setq *current-project* "project1"))
-
- |#
- ;;; -----------------------------------------------------------------------------------------
- (defun on-server-p nil (string-equal (machine-instance) *server-name*))
-
- (defmacro concat (&rest strings)
- `(concatenate 'string ,@strings))
-
- (defun check-server-p nil
- (if (null (probe-file *expanded-server-volume*))
- (progn
- (message-dialog (concat "RCS Error, Could not find server " *server-volume* ". -- Aborting."))
- nil)
- t))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Locking protocol stuff
-
- ;;; returns t if successful and nil if not successful
- ;;; Use file write locking on server to make sure only one person at a time can make changes to
- ;;; the master file of locked files.
- (defun set-lock ()
- (let (time)
- ; (when *lock-id* (return-from set-lock nil))
- (setq time (get-universal-time))
- (loop
- (setq *lock-id*
- (handler-case (open *lock-name* :direction :output :if-exists :supersede)
- (file-error () nil)))
- (when *lock-id* (return *lock-id*))
- (when (and (> (- (get-universal-time) time) *max-lock-wait-time*)
- (prog1
- (not (y-or-n-dialog (concat "I have waited longer than "
- (prin1-to-string *max-lock-wait-time* )
- " seconds to get a lock. Should I wait longer?")
- :cancel-text nil))
- (setq time (get-universal-time))))
- (return nil)))))
-
- (defun destroy-lock ()
- (if *lock-id* (close *lock-id*)
- ;; just in case some fool (like me) calls set-lock outside of a with-transaction
- ;; and forgets to close it
- (dolist (s *open-file-streams*)
- (when (string-equal (namestring (stream-filename s)) *lock-name*)
- (close s))))
- (setq *lock-id* nil)
- )
-
- ;;; Locking should be done with this macro so that destroy-lock is always done.
- (defmacro with-transaction (&body body)
- `(unwind-protect (if (set-lock) (progn ,@body))
- (without-interrupts (destroy-lock)))) ; make sure destroy-lock gets to finish
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ; Support for multiple projects
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (defun get-project (project)
- (assoc project *rcs-project-list* :test #'string-equal))
-
- (defun register-project (info-list)
- "register a project with RCS.
- info-list is (name-of-project server-name home-directory server-volume folder-of-server-volume
- backup-folder backup-versions)"
- (setq *rcs-project-list* (delete info-list *rcs-project-list*
- :test #'(lambda (x y) (string= (car x) (car y)))))
- (push info-list *rcs-project-list* ))
-
- (defun change-project (project &optional (init t))
- (let* ((list (get-project project))
- (project-name (first list))
- (server-name (second list))
- (home-dir (third list))
- (server-volume (fourth list))
- (folder-on-server (fifth list))
- (backup-folder (sixth list))
- (backup-versions (seventh list)))
-
- (setq *last-directory-locked* nil)
- (setq *current-project* project-name)
- (setq *server-name* server-name)
- (setq *home-directory-o* home-dir)
- (setq *server-volume* server-volume)
- (setq *folder-of-server-volume-on-server* folder-on-server)
- (setq *rcs-backup-folder* backup-folder)
- (setq *rcs-backup-versions* backup-versions)
- (when init (init-rcs))
- ))
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ;;; (expand-host "ccl:foo:bar") --> "HD:MCL 2.0:foo:bar"
- ;;; Note that ccl:foo:bar is not a legal logical pathname (no semicolons between directories)
- (defun expand-host (path)
- (let ((pos (search ":" path))
- hostname rest)
- (when (null pos) (error "no host for expand-host"))
- (setq hostname (subseq path 0 (1+ pos)))
- (setq rest (subseq path (1+ pos)))
- (concat (namestring (translate-logical-pathname hostname)) rest)))
-
- (defun get-host (path)
- (subseq path 0 (1+ (search ":" path))))
-
- ;;; init-rcs is called automatically at the end of this file
- (defun init-rcs nil
- (when (probe-file *rcs-init-file-name*) (load *rcs-init-file-name*))
- (let (server-servers-volume host-server-volume change-project-menu-item)
- (setq host-server-volume (get-host *server-volume*))
- (if (search ";" *home-directory-o*)
- (setq *home-directory* (namestring (translate-logical-pathname *home-directory-o*)))
- (setq *home-directory* (expand-host *home-directory-o*))) ;non legal pathname. expand host.
- (setq *expanded-server-volume* *server-volume*)
- (when (on-server-p)
- (setq server-servers-volume (concat (expand-host *folder-of-server-volume-on-server*)
- (get-host *server-volume*)))
- (setf (logical-pathname-translations
- ;; take out the colon at the end of *server-volume*
- (subseq host-server-volume 0 (1- (length host-server-volume))))
- ;; copied right out of steele without understanding it...
- `(("**;*.*.*" ,(concat server-servers-volume "**"))))
- (setq *expanded-server-volume* (expand-host *server-volume*))
- )
- (when (null (check-server-p)) (return-from init-rcs))
- ;;; check to see if the following two statements do the right thing ZZZ
- (setq *locked-file-list-file* (concat *server-volume* *filename-locked-file-list-file*))
- (setq *log-file* (concat *server-volume* *filename-log-file*))
- (setq *lock-name* (concat *expanded-server-volume* *filename-lock-name*))
- (pushnew *locked-file-list-file* *files-not-to-copy* :test #'string-equal)
- (when *rcs-backup-folder*
- (pushnew *rcs-backup-folder* *files-not-to-copy* :test #'string-equal))
- (pushnew (concat *server-volume* *filename-lock-name*) *files-not-to-copy* :test #'string-equal)
- (pushnew *log-file* *files-not-to-copy* :test #'string-equal)
- (if (find-menu "LockFile") (menu-deinstall *rcs-menu*))
- (setq *rcs-menu* (make-instance 'menu :menu-title "LockFile"))
- (add-menu-items *rcs-menu*
- (make-instance 'menu-item
- :menu-item-title "Lock - (a project file and copy to local disk)"
- :menu-item-action 'lock-project-file
- :help-spec
- (format nil "Lock a file. This brings up a dialog so that the ~
- user can choose a file to lock. If the file is ~
- already locked then the user gets an error message. ~
- Locking a file copies the file from the server to ~
- the local hard disk."))
- (make-instance 'menu-item
- :menu-item-title "Unlock - (a project file and copy back to server)"
- :menu-item-action 'unlock-project-file
- :help-spec
- (format nil "Unlock a file and copy to server. This brings up ~
- a dialog with all your locked files. Select 1 or ~
- more files (with shift-click) to unlock. The ~
- files are copied back to the server."))
- (make-instance 'menu-item
- :menu-item-title "Forget - (Unlock project file but don't copy new version to server)"
- :menu-item-action 'unlock-file-dont-copy
- :help-spec
- (format nil "Unlock a file, but don't copy to server. This is ~
- like 'Unlock' but the files are not copied to ~
- the server. Useful when the user changes his/her ~
- mind about making the changes permanent."))
- (make-instance 'menu-item
- :menu-item-title "Copy - (newly created file to server.)"
- :menu-item-action 'copy-new-file-to-server
- :help-spec
- (format nil "Copy a newly created file to the server. The user ~
- has just created a file on his/her hard disk. ~
- To move it to the server choose this."))
- ; (make-instance 'menu-item
- ; :menu-item-title "Copy logged files to local disk."
- ; :menu-item-action 'copy-logfiles-to-local-disk)
- (make-instance 'menu-item
- :menu-item-title "Update - (files on local disk)"
- :menu-item-action #'(lambda nil (eval-enqueue
- '(copy-directory-1 *expanded-server-volume* *home-directory*)))
- :help-spec
- (format nil "Copy server directory to local disk. ~
- Updates all files on local disk."))
- (make-instance 'menu-item
- :menu-item-title "Show locked - (all locked files)"
- :menu-item-action 'find-all-locked-files
- :help-spec
- (format nil "Show a list of all the locked files, ~
- along with who locked them."))
- (make-instance 'menu-item
- :menu-item-title "Show changes - (made to project files)"
- :menu-item-action 'show-log-file
- :help-spec
- (format nil "Show a list of past changes to all files."))
- (make-instance 'menu-item
- :menu-item-title "Show file comments - (for this file)"
- :menu-item-action 'show-log-for-top-window
- :help-spec
- (format nil "Show a list of past changes for topmost file."))
- (make-instance 'menu-item
- :menu-item-title "Help"
- :menu-item-action 'show-help)
- )
-
- (when *rcs-project-list*
- (add-menu-items *rcs-menu*
- (setq change-project-menu-item
- (make-instance 'menu-item
- :menu-item-title (format nil "Change Project from ~a" *current-project*)
- :menu-item-action
- #'(lambda nil
- (let (result)
- (setq result
- (catch-cancel
- (select-item-from-list
- (mapcar #'car
- (remove *current-project* *rcs-project-list*
- :test #'(lambda (x y) (equal x (car y)))))
- :window-title "Select new project"
- )))
- (when (neq result :cancel)
- (eval-enqueue `(progn (change-project ,(car result))
- (set-menu-item-title ,change-project-menu-item
- (format nil "Change Project from ~a" ,(car result))))))))
- :help-spec
- (format nil "Switch to a different project.")
- ))))
-
-
- (menu-install *rcs-menu*)
-
- (load-locked-file-list)
-
- (unless (member 'init-rcs *lisp-startup-functions*)
- (setf *lisp-startup-functions*
- (nconc *lisp-startup-functions* (list 'init-rcs))))
-
-
- ))
-
-
- ;;; ================================================================================
- ;;; Backup stuff
- ;;; Backup files are kept in a file structure in *rcs-backup-folder*. The leaf nodes in
- ;;; this structure are a little different than what is one the server. Server file names
- ;;; are a folder name on the backup folder and versions of that file are numbered files
- ;;; within that folder.
- ;;; e.g.
- ;;; server:baz:biz.lisp on the server could have the following backups
- ;;; backup:baz:biz.lisp:1
- ;;; backup:baz:biz.lisp:2
- ;;; backup:baz:biz.lisp:3
- ;;; backup:baz:biz.lisp:4
- (defun server-to-backup-folder-name (serverfile)
- (let ((name (file-namestring serverfile))
- (stripped (strip-left *expanded-server-volume* (directory-namestring serverfile))))
- (concat (expand-host *rcs-backup-folder*)
- stripped
- name
- ":"
- )))
-
- (defun backup-server-file (serverfile)
- (when *rcs-backup-folder*
- (let* ((backup-folder (server-to-backup-folder-name serverfile))
- (backups (mapcar #'(lambda (x) (read-from-string (file-namestring x)))
- (directory (concat backup-folder "*"))))
- (last-num (or (and backups (apply #'max backups)) 0))
- first-num
- )
- (when (probe-file serverfile)
- (copy-file serverfile (concat backup-folder (prin1-to-string (1+ last-num)) "."))
- (when (numberp *rcs-backup-versions*)
- (dotimes (i (- (1+ (length backups)) *rcs-backup-versions*))
- (setq first-num (apply #'min backups))
- (delete-file (concat backup-folder (prin1-to-string first-num) "."))
- (setq backups (delete first-num backups))))))))
-
- ;;; ================================================================================
-
- (defun server-to-logical-server-name (file)
- (concat *server-volume*
- (strip-left *expanded-server-volume* file)))
-
- ;;; lock a file
- (defun lock-project-file nil
- (let (fromserverfile from-logical-server-file
- tofile
- tofileyounger
- within
- (server-volume *expanded-server-volume*)
- (default-choose-directory (choose-file-default-directory))
- )
- (when (string-equal (machine-instance) "")
- (message-dialog "Aborted because you have not named your Mac. Please name your computer in Sharing Setup in Control Panels.")
- (return-from lock-project-file))
- (when (null (check-server-p)) (return-from lock-project-file))
- (setq fromserverfile
- (catch-cancel
- (choose-file-dialog :directory (if *last-directory-locked* *last-directory-locked*
- *expanded-server-volume*)
- :button-string "Lock file"
- )))
- (set-choose-file-default-directory default-choose-directory)
- (when (neq fromserverfile :cancel)
- (setq fromserverfile (namestring fromserverfile))
- (setq within (search server-volume fromserverfile :test #'string-equal))
- (when (or (null within) (not (zerop within)))
- (message-dialog
- (concat "File to be locked was not contained within the server: " server-volume " -- Aborting command."))
- (return-from lock-project-file))
- (setq *last-directory-locked* (directory-namestring fromserverfile))
- (setq from-logical-server-file (server-to-logical-server-name fromserverfile))
- (with-transaction
- (when (is-locked-filep from-logical-server-file)
- (destroy-lock) ;; just in case someone takes too long in the message-dialog below
- (message-dialog (concat from-logical-server-file " is already locked. Aborting command."))
- (return-from lock-project-file))
- (setq tofile (logicalserver-to-home-name from-logical-server-file))
- (setq tofileyounger (is-youngerp tofile fromserverfile))
- (when (or (not tofileyounger)
- (and tofileyounger
- ;; hopefully not much time will be spent in this dialog
- ;; because the lock is set in with-transaction
- (eq t (catch-cancel (y-or-n-dialog
- "WARNING! The file on the local disk is younger than the one on the server. Should I still copy it and so delete your version?")))))
- (if (probe-file tofile) (unlock-file tofile))
- (update-locked-file-list from-logical-server-file :add)
- (destroy-lock) ;;kind of ugly, but it should be done here
- (when (null (is-same-age fromserverfile tofile))
- (copy-file fromserverfile tofile
- :if-exists :supersede))
- (let ((window (my-find-window tofile)))
- (when window (window-close window)))
- (when
- (and (eq (mac-file-type tofile) :TEXT)
- (y-or-n-dialog
- (concat fromserverfile " has been copied to your disk and is locked. To edit the file click on EDIT, otherwise click on OK.")
- :yes-text "EDIT" :no-text "OK" :cancel-text nil))
- (ed tofile))
- )))))
-
- (defun my-find-window (name)
- (setq name (pathname name))
- (dolist (w (windows) nil)
- (when (equal name (window-filename w)) (return w))))
-
- (defun is-youngerp (file1 file2)
- (and (probe-file file1) (probe-file file2) (> (file-write-date file1) (file-write-date file2))))
-
- (defun is-same-age (file1 file2)
- (and (probe-file file1) (probe-file file2) (eql (file-write-date file1) (file-write-date file2))))
-
- ;;; format of locked-file-list is ((filename . person) (filename . person) ...)
-
- (defun is-locked-filep (filename)
- (load-locked-file-list)
- (assoc filename *locked-file-list*
- :test #'string-equal))
-
- (defun load-locked-file-list nil
- (let ((expanded (expand-host *locked-file-list-file*)))
- (if (null (probe-file expanded))
- (with-open-file (stream expanded :direction :output)
- (print nil stream)))
- (setq *locked-file-list*
- (with-open-file (stream expanded :direction :input)
- (read stream)))))
-
- (defun save-locked-file-list nil
- (let* ((expanded (expand-host *locked-file-list-file*))
- (tempfilename (concat expanded "temp")))
- (with-open-file (stream tempfilename :direction :output :if-exists :supersede)
- (print *locked-file-list* stream))
- (rename-file tempfilename expanded :if-exists :supersede)))
-
- (defun username nil (machine-instance))
-
- (defun make-pair (&key filename person)
- (cons filename person))
- (defun get-person (pair)
- (rest pair))
- (defun get-filename (pair)
- (first pair))
-
- ;;; ------------------------------------------------------------------------------------
- ;;; unlock a file
- (defun unlock-project-file (&optional (dontcopyflag nil))
- (let ((username (machine-instance))
- (homefilename)
- (serverfilenames))
- (when (eql username "")
- (message-dialog "Aborted because you have not named your Mac. Please name your computer in Sharing Setup in Control Panels.")
- (return-from unlock-project-file))
- (when (null (check-server-p)) (return-from unlock-project-file))
- (setq serverfilenames
- (catch-cancel
- (select-item-from-list (find-my-locked-files) :selection-type :disjoint
- :default-button-text
- (if dontcopyflag "Forget" "Unlock"))))
- (when (neq serverfilenames :cancel)
- (dolist (serverfilename serverfilenames)
- ;; doncopyflag means unlock the file but don't copy your version to the project directory
- (setq homefilename (logicalserver-to-home-name serverfilename))
- (when (null dontcopyflag)
- (if (probe-file homefilename)
- (copy-to-server-and-update-logfile homefilename serverfilename
- (expand-host serverfilename))
- (format t "You do not have ~a to copy to the project directory~%" homefilename))
- )
- ;; (let ((window (find-window (pathname-name homefilename))))
- ;; (when window (window-close window))) JL--closing the homefile window, if its here
- ;; (lock-file homefilename) JL--locking the homefile
-
- ;; bug. if a lock cannot be gotten then the locked file list will not be updated. Yet
- ;; the log file will be updated and the new file will be on the server.
- (with-transaction
- (update-locked-file-list serverfilename :delete))
- ))))
-
- ;;; Given a name on the server, construct the corresponding name on the home directory.
- (defun logicalserver-to-home-name (filename)
- (concat *home-directory*
- (strip-left *server-volume* (namestring filename))))
-
- ;;; Given a name on the home directory, construct a name for the server
- (defun home-to-server-name (filename)
- (concat *server-volume*
- (strip-left *home-directory* (namestring filename))))
-
- (defun copy-to-server-and-update-logfile (homefilename serverfilename expandedserverfilename)
- (when (or (null (probe-file expandedserverfilename))
- (>= (file-write-date homefilename) (file-write-date expandedserverfilename))
- (eq t (catch-cancel (y-or-n-dialog
- "WARNING! The file on the local disk is older than the one on the server. Should I still copy it?"))))
- (when (probe-file expandedserverfilename)
- (unlock-file expandedserverfilename)) ;;JL--unlock the serverfile if it's there
- (backup-server-file expandedserverfilename)
- (copy-file homefilename expandedserverfilename :if-exists :supersede)
- (lock-file (expand-host serverfilename)) ;so user doesn't accidently edit it
- ;; (let ((window (find-window (pathname-name homefilename))))
- ;; (when window (window-close window))) JL--close the homefile window if its there
- ;; (lock-file homefilename) JL--lock the homefile (now that window is closed
- ;; make sure the dates on both files are the same in case clocks are off on
- ;; both machines.
- (set-file-write-date homefilename (file-write-date expandedserverfilename))
- (update-log-file serverfilename)
- ))
-
- (defun copy-new-file-to-server nil
- (let (homefilename serverfilename expandedserverfilename within)
- (message-dialog "Please select a newly created file to copy to the server.")
- (setq homefilename
- (catch-cancel (choose-file-dialog :directory *home-directory*
- )))
- (when (neq homefilename :cancel)
- (setq homefilename (namestring homefilename))
- (setq within (search *home-directory* homefilename :test #'string-equal))
- (when (or (null within) (not (zerop within)))
- (message-dialog
- (concat "New file was not contained within " *home-directory* " -- Aborting command."))
- (return-from copy-new-file-to-server))
- (setq serverfilename (home-to-server-name homefilename))
- (setq expandedserverfilename (expand-host serverfilename))
- (when (probe-file expandedserverfilename)
- (message-dialog (concat serverfilename " already exists. Aborting command."))
- (return-from copy-new-file-to-server))
- (copy-to-server-and-update-logfile homefilename serverfilename expandedserverfilename)
- )))
-
- (defun update-locked-file-list (file operation)
- (load-locked-file-list)
- (let ((newpair (make-pair :filename file :person (username))))
- (cond
- ((eq operation :add)
- (pushnew newpair *locked-file-list*))
- ((eq operation :delete)
- (setq *locked-file-list*
- (delete newpair *locked-file-list* :test #'equal)))
- (t (error "illegal operation in update-locked-file-list")))
- (save-locked-file-list)))
-
- ;(defun modeless-get-string-from-user (message &rest x)
- ; (let (dialog finishedflag)
- ; (setq dialog
- ; (apply #'get-string-from-user message
- ; :modeless t
- ; :action-function #'(lambda (string) (setq finishedflag string))
- ; x))
- ; (loop (dotimes (i 10) (event-dispatch)) (when (or (null (wptr dialog)) finishedflag) (window-close dialog) (return finishedflag)))
- ; ))
-
- (defun update-log-file (filename)
- (setq filename (namestring filename))
- (let ((changes))
- (with-open-file (stream (expand-host *log-file*) :direction :output :if-exists :append :if-does-not-exist :create)
- (setq changes (catch-cancel ;catch-cancel & cancel-text are not needed for modeless-get...
- (get-string-from-user (concat "File " filename " has been copied to the server. Describe your changes to the file here.")
- :cancel-text "No msg")))
- (format stream "~a \"~a\" ~a -- ~a~%" (machine-instance) filename (return-the-date) changes)
- )))
-
- (defun return-the-date nil
- (multiple-value-bind (second minute hour date month year
- day-of-week daylight-saving-timep time-zone)
- (get-decoded-time)
- (declare (ignore second year day-of-week daylight-saving-timep time-zone))
- (format nil "(~a:~2,'0d ~a/~2,'0d)" hour minute month date)))
-
- (defun find-my-locked-files nil
- (find-user-locked-files (username)))
-
- (defun find-user-locked-files (user)
- (mapcar 'get-filename
- (remove user *locked-file-list*
- :test #'(lambda (user y) (not (equal user (get-person y)))))))
-
- (defun find-people-with-locked-files nil
- (let (people)
- (dolist (pair *locked-file-list*)
- (pushnew (get-person pair) people :test #'equal))
- people))
-
- (defun find-all-locked-files nil
- (load-locked-file-list)
- (show-listener)
- (format t "~%--Locked file list--~%")
- (if (null *locked-file-list*) (format t "There are no locked files.")
- (dolist (person (find-people-with-locked-files))
- (format t "Locked files for ~a:~%" person)
- (dolist (file (find-user-locked-files person))
- (format t " ~a~%" file)))))
-
- (defun show-listener nil
- (window-select (find-window "Listener")))
-
- (defun unlock-file-dont-copy nil
- (unlock-project-file t))
-
- ;;; copy a file and make sure the write dates are the same on both files
- (defun copy-file-and-set-write-date (fromfile tofile)
- (copy-file fromfile tofile :if-exists :supersede)
- (set-file-write-date tofile (file-write-date fromfile)))
-
- ;;;-----
- ;;; Copy files from logfile to local disk. Remove duplicate names in logfile list of files.
- ;;; BUGS: doesn't check to see if local files are more recent than server files.
- ;;; This function is currently not being used and has bugs since it hasn't been updated.
- #|
- (defun copy-logfiles-to-local-disk nil
- (let (linelist selectlist tofile fromfilelist)
- (with-open-file (finput *log-file* :direction :input)
- (setq linelist
- (do* ((line (read-line finput nil :eof)(read-line finput nil :eof))
- (linelist)
- (pos))
- ((eq line :eof) linelist)
- (setq pos (position #\" line)) ;kludge for testing for a filename in line
- (if pos
- (push line linelist)))))
- (setq selectlist
- (catch-cancel
- (select-item-from-list linelist :selection-type :disjoint)))
- (when (and selectlist (not (eq selectlist :cancel)))
- (show-listener)
- (setq fromfilelist
- (mapcar #'(lambda (line) (read-from-string line nil nil :start (position #\" line)))
- selectlist))
- (setq fromfilelist (remove-duplicates fromfilelist :test #'string-equal))
- (dolist (fromfile fromfilelist)
- (if (probe-file fromfile)
- (progn
- (setq tofile (server-to-home-name fromfile))
- (format t "~%About to copy file ~a to ~a -- " fromfile tofile)
- (copy-file-and-set-write-date fromfile tofile)
- (format t "DONE"))
- (format t "~%Did not copy file ~a because I could not find it." fromfile))))))
-
- |#
-
- (defun rcs-directoryp (string)
- (eql #\: (char string (1- (length string)))))
-
- (defun copy-directory-1 (from to)
- (let
- ((stream (make-instance 'fred-window
- :window-title "Update Log"
- :scratch-p t))
- (copyflag (if (member from *files-not-to-copy* :test #'string-equal)
- nil t)))
- (format stream "~%About to copy ~s to ~s ~%" from to)
- (select-directories-to-not-copy)
- (copy-directory from to t nil stream copyflag)
- (format stream "~%DONE!~%")
- (fred-update stream)
- ))
-
- (defun directories-in-files-dont-copy ()
- (let (result)
- (dolist (item *files-not-to-copy*)
- (when (rcs-directoryp item) (push item result)))
- result))
-
- (defmethod my-set-table-sequence ((item sequence-dialog-item) new-seq)
- (set-table-sequence item new-seq)
- (set-cell-size item
- (default-cell-size item))
- (set-visible-dimensions item (table-dimensions item)))
-
- (defun add-a-directory (dialog-item)
- (let (result within thesequenceitem)
- (setq result (catch-cancel (choose-directory-dialog :directory *expanded-server-volume*)))
- (when (not (eq result :cancel))
- (setq result (namestring result))
- (setq within (search *expanded-server-volume* result :test #'string-equal))
- (if (or (null within) (not (zerop within)))
- (message-dialog
- (concat "Directory was not contained within the server: " *expanded-server-volume* " -- Aborting command."))
- (pushnew (server-to-logical-server-name (namestring result)) *files-not-to-copy*
- :test #'string-equal))
- (setq thesequenceitem (find-named-sibling dialog-item 'sequence))
- (my-set-table-sequence thesequenceitem *files-not-to-copy*))))
-
- (defun delete-a-file-or-directory (dialog-item)
- (let (thesequenceitem cells)
- (setq thesequenceitem (find-named-sibling dialog-item 'sequence))
- (setq cells (selected-cells thesequenceitem))
- (if (null cells) (message-dialog "Nothing was selected to delete")
- (progn
- (dolist (cell cells)
- (setq *files-not-to-copy*
- (delete (cell-contents thesequenceitem cell) *files-not-to-copy*
- :test #'string-equal)))
- (my-set-table-sequence thesequenceitem *files-not-to-copy*)))))
-
- (defun select-directories-to-not-copy ()
- (modal-dialog
- (make-instance 'window :view-size #@(550 300)
- :window-title "Select directories within the server to not copy."
- :view-subviews
- `(
- ,(make-instance 'static-text-dialog-item
- :dialog-item-text "List of Files/Directories not to copy"
- :view-position #@(10 0))
- ,(make-instance 'sequence-dialog-item
- :table-sequence *files-not-to-copy*
- :view-position #@(10 30)
- :view-nick-name 'sequence)
- ,(make-instance 'button-dialog-item
- :dialog-item-action #'delete-a-file-or-directory
- :dialog-item-text "Delete file/directory from list"
- :view-position #@(300 10))
- ,(make-instance 'button-dialog-item
- :dialog-item-action #'add-a-directory
- :dialog-item-text "Add directory to list"
- :view-position #@(300 35))
- ,(make-instance 'button-dialog-item
- :dialog-item-action #'(lambda (item) (window-close (view-window item)))
- :dialog-item-text "Done"
- :view-position #@(300 60))
- ,(make-instance 'static-text-dialog-item
- :dialog-item-text "FYI - List of Files/Directories to copy"
- :view-position #@(10 150))
- ,(make-instance 'sequence-dialog-item
- :table-sequence *files-to-copy*
- :view-position #@(10 180)
- :view-nick-name 'sequence2)
-
- ))))
-
- (defun dont-copyp (file-or-directory)
- (member (server-to-logical-server-name file-or-directory) *files-not-to-copy* :test #'string-equal))
- (defun do-copyp (file-or-directory)
- (member (server-to-logical-server-name file-or-directory) *files-to-copy* :test #'string-equal))
-
- ;;; copy one directory to another directory
- ;;; verboseflag,if true, prints out a DOT when a file is read in
- ;;; purge, if true, deletes the destination directory
- (defun copy-directory (from to &optional (verboseflag t) (purge nil) (stream t) (copyflag t))
- (setq from (namestring from)
- to (namestring to))
- (unless (and (rcs-directoryp from) (probe-file from) (rcs-directoryp to) (not (equal from to)))
- (cond
- ((null (rcs-directoryp from)) (format stream "~s is not a directory name, aborted" from))
- ((null (probe-file from)) (format stream "Could not find directory ~s, aborted" from))
- ((null (rcs-directoryp to)) (format stream "~s is not a directory name, aborted" to))
- ((equal from to) (format stream "~s, source and destination directories are the same, aborted")))
- (return-from copy-directory))
- ; (if (and copyflag (or purge (null (probe-file to)))) (create-file to :if-exists nil))
- (dolist (fromfile (list-of-files from))
- (let* ((filename (file-namestring fromfile))
- (tofile (merge-pathnames to filename))
- (tofilepresent (probe-file tofile))
- (fromfilewritedate (file-write-date fromfile))
- (tofilewritedate (and tofilepresent (file-write-date tofile))))
- ;; if current directory is set up to copy check to see if file is excluded
- ;; if current directory is set up not to copy check to see if file is included
- (cond ((dont-copyp (namestring fromfile)))
- ((and (not copyflag) (not (do-copyp (namestring fromfile)))))
- ((or (null tofilepresent)
- (< tofilewritedate fromfilewritedate))
- (if tofilepresent (unlock-file tofile))
- (copy-file fromfile tofile :if-exists :supersede)
- ; (lock-file tofile)
- (when verboseflag (format stream "~%~a copied." fromfile))
- ;; delete fasl file if copying over a .lisp file
- (let ((fasl (convert-to-fasl tofile)))
- (when (and fasl (probe-file fasl))
- (delete-file fasl)
- (when verboseflag (format stream "~%~a was deleted" (namestring fasl)))))
- (set-file-write-date tofile fromfilewritedate)
- (when (and verboseflag (typep stream 'fred-window)) (fred-update stream))
- )
- ((and tofilewritedate (> tofilewritedate fromfilewritedate))
- (format stream "~%Your version of ~a is newer than the server's version so it was left untouched."
- tofile)))))
- (dolist (dir (directory (concat from "*.*") :directories t :files nil)) ;mac specific
- (let* ((newfromdir (directory-namestring dir))
- (newpartdir (strip-left from newfromdir))
- (newtodir (concat to newpartdir)))
- (cond ((do-copyp newfromdir) (copy-directory newfromdir newtodir verboseflag purge stream t))
- ((dont-copyp newfromdir) (copy-directory newfromdir newtodir verboseflag purge stream nil))
- (t (copy-directory newfromdir newtodir verboseflag purge stream copyflag))
- ))))
- ; (when (not (dont-copyp newfromdir)) (copy-directory newfromdir newtodir verboseflag purge stream)))))
-
- (defun convert-to-fasl (lispname)
- (if (equal "lisp" (pathname-type lispname))
- (make-pathname
- :host (pathname-host lispname)
- :device (pathname-device lispname)
- :directory (pathname-directory lispname)
- :name (pathname-name lispname)
- :type "fasl")
- nil))
-
- ;;; strip (length sub) characters from the left part of seq
- ;;; Used to strip off part of a directory from seq
- ;;; e.g. (strip-left "hd:" "hd:foo:") --> "foo:"
- (defun strip-left (sub seq)
- (subseq seq (length sub)))
-
- ;;; Return a list of files in directory "dir"
- (defun list-of-files (dir)
- (directory (concat dir "*.*")))
-
- (defun show-help ()
- (message-dialog
- " User choices from the lockfile menu:
- - Lock a file. This brings up a dialog so that the user can choose a
- file to lock.
- If the file is already locked then the user gets an error message.
- Locking a file copies the file from the server to the local hard disk.
-
- - Unlock a file and copy to server. This brings up a dialog with all
- your locked files.
- Select 1 or more files (with shift-click) to unlock.
- The files are copied back to the server.
-
- - Forget. Unlock a file, but don't copy to server.
- This is like the choice above but the files are not copied to the server.
- Useful when the user changes his/her mind about making the
- changes permanent.
-
- - Copy a newly created file to the server.
- The user has just created a file on his/her hard disk.
- To move it to the server choose this.
-
- - Update - copy server directory to local disk.
- Updates all files on local disk.
-
- - Show all locked files.
- Show a list of all the locked files, along with who locked them.
-
- - Show a list of past changes to files.
-
- - File Comments
- Show a list of past comments on changes for topmost file.
- "
- :size (make-point *screen-width* (- *screen-height* 40))))
-
- (defun show-log-file nil
- (let ((win (make-instance 'fred-window
- :window-title "RCS Change Log"
- :scratch-p t)))
- (buffer-insert-file (fred-display-start-mark win)
- (expand-host *log-file*))
- (fred-update win)))
-
- (defun show-log-for-top-window nil
- (let (win filename fredwindow linelist)
- (setq win (first (windows)))
- (when (window-filename win)
- (setq filename (file-namestring (window-filename win)))
- (setq fredwindow (make-instance 'fred-window
- :window-title (concat "Comments of " filename)
- :scratch-p t))
- (with-open-file (finput (expand-host *log-file*) :direction :input)
- (do* ((line (read-line finput nil :eof)(read-line finput nil :eof)))
- ((eq line :eof))
- (push line linelist)))
- (dolist (line linelist)
- (when (search filename line :test #'string-equal) (princ line fredwindow) (terpri fredwindow)))
- (fred-update fredwindow))))
-
- ;;; ------------------------------------------------------------------------------
- (init-rcs)